#Author: Salavatore Gionfriddo
#key decorators: traced, memoized, curried
##        self.__name__ = self.func.__name__
##        self.__doc__ = self.func.__doc__
##        self.__dict__.update(self.func.__dict__)
##        further work needs to be done to ensure the signature of the wrapped
##        function has the same signature as the original function
#syntax:
#@decorator
#func():
#  pass

class traced(object):
    def __init__(self, func):
        self.func = func
        self.func_code = func.func_code
        self.func_name = func.func_name
    def __call__(self, *args, **kw):
        def traced_function(*args, **kw):
            print "Calling %s with args %s, %s" % (self.func.func_name, args, kw)
            return self.func(*args, **kw)
        return traced_function(*args, **kw)

class curried(object):
    def __init__(self, func, *a, **k):
        self.func = func
        self.func_code = func.func_code
        self.func_name = func.func_name
        self.args = a
        self.kw = k
    def __call__(self, *a, **k):
        args = self.args + a
        kw = self.kw.copy()
        kw.update(k)
        size = len(args)
        if kw: size += len(kw)
        if size < self.func.func_code.co_argcount:
            if kw: result = curried(self.func, *args, **kw)
            else: result = curried(self.func, *args)
        else:
            if kw: result = self.func(*args, **kw)
            else: result = self.func(*args)
        return result

class memoized(object):
    def __init__(self, func):
        self.func = func
        self.func_code = func.func_code
        self.func_name = func.func_name
        self.cache = {}
        self.OVERRIDE_MEMOIZED_VALUE = 'Re_Evaluate'
    def __call__(self, *args, **kw):
        try:
            if self.OVERRIDE_MEMOIZED_VALUE in kw:
                OVERRIDE = kw.pop(self.OVERRIDE_MEMOIZED_VALUE)
                if OVERRIDE:
                    raise KeyError
            return self.cache[args]
        except KeyError:
            self.cache[args] = value = self.func(*args)
            return value
        except TypeError:
            # uncachable -- for instance, passing a list as an argument.
            # Better to not cache than to blow up entirely.
            return self.func(*args)
